package com.winning.pmph.service;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.winning.pmph.entity.*;
import com.winning.pmph.mapper.ExamBookMapper;
import com.winning.pmph.utils.Const;
import com.winning.pmph.utils.ExamBookUtil;
import com.winning.pmph.utils.ParseBookZipUtil;
import com.winning.pmph.utils.StringUtil;
import com.winning.pmph.vo.CascaderOption;
import com.winning.pmph.vo.ExamBookCatalog;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileFilter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Component
@Transactional
public class ExamBookService extends ServiceImpl<ExamBookMapper, ExamBook> {
    @Resource
    private ExamBookArticleService examBookArticleService;
    @Resource
    private ExamBookCatalogItemContentService examBookCatalogItemContentService;
    @Resource
    private ExamBookCatalogItemService examBookCatalogItemService;
    @Resource
    private ExamBookPicService examBookPicService;
    @Resource
    private ExamBookTableService examBookTableService;
    @Resource
    private ExamOutlineTagService examOutlineTagService;
    @Resource
    private ExamOutlineService examOutlineService;
    @Resource
    private ExamOutlineDetailService examOutlineDetailService;
    @Resource
    private ExamKnowledgeService examKnowledgeService;
    @Resource
    private BookCatalogItemService bookCatalogItemService;

    public void importBook(ExamBook examBook, String header, String userName) {
        // 解压图书;
        ParseBookZipUtil.unzipBook(examBook.getPath(), header);
        /*String analysisPath = ExamBookUtil.recursiveFindFile(new File(StringUtils.removeEnd(examBook.getPath(), ".zip")));

        if (StringUtils.isBlank(analysisPath)) {
            throw new RuntimeException("未查找到同时包含meta.xml，book.xml，book_catalog.xml，pics的文件夹");
        }
        examBook.setAnalysisPath(analysisPath);
        // 解析图书, 图书元数据及文章;
        ExamBook book = ExamBookUtil.parseBook(examBook);// 校验ISBN号是否已存在;
        List<ExamBook> books = lambdaQuery().eq(ExamBook::getIsbn, book.getIsbn()).list();
        if (books.size() > 0) {
            throw new RuntimeException("图书ISBN号已存在, 请删除后重新导入!");
        }
        // 保存图书;
        saveOrUpdate(book);
        // 移动图片文件夹;
        String picsTargetDirPath = ExamBookUtil.copyPicAndTable(analysisPath, book);
//        // 解析pics包内xml;
        ExamBookUtil.parsePicAndTable(book, new File(picsTargetDirPath));
//        // 保存图片;
        saveBookPic(book);
        // 修改文章图片路径并保存;
        saveBookArticle(book);
        // 解析目录;
        ExamBookCatalog bookCatalog = ExamBookUtil.parseBookCatalog(analysisPath);
        // 保存目录;
        saveBookCatalog(book, bookCatalog);*/
        // 保存大纲
        File file = new File(StringUtils.removeEnd(examBook.getPath(), ".zip"));
        File[] xlsxFiles = file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().endsWith(".xlsx");
            }
        });
        if (xlsxFiles == null || xlsxFiles.length > 1) {
            throw new RuntimeException("大纲不存在或者存在多个大纲");
        }

        ExamOutline examOutline = new ExamOutline();
        examOutline.setId(IdWorker.getIdStr());
        examOutline.setCreatedBy(userName);
        examOutline.setUpdatedBy(userName);
        examOutlineService.uploadAndInsertFile(xlsxFiles[0].getPath(), examOutline);
        examBook.setExamOutlineId(examOutline.getId());
        // 根据知识查找大纲
        List<ExamKnowledge> examKnowledgeList = matchOutlineByKnowledge(examBook, examOutline);
        updateKnowledgeNum(examKnowledgeList, examOutline);
        examBook.setStatus(Const.IMPORT_STATUS_SUCCESS);
    }

    /**
     * 根据知识更新知识数量
     *
     * @param examKnowledgeList
     * @param examOutline
     */
    public void updateKnowledgeNum(List<ExamKnowledge> examKnowledgeList, ExamOutline examOutline) {
        Map<String, Long> countMap = examKnowledgeList.stream().collect(Collectors.groupingBy(ExamKnowledge::getOutlineDetailId, Collectors.counting()));
        // 更新要点对应的知识点数
        examOutline.getDetailList().forEach(detailItem -> {
            detailItem.setKnowledgeNum(countMap.getOrDefault(detailItem.getId(), 0L));
        });
        // 更新要点
        examOutlineDetailService.updateBatchById(examOutline.getDetailList());
        // 更新大纲对应的总知识点数
        examOutline.setKnowledgeNum(examOutline.getDetailList().stream().mapToLong(ExamOutlineDetail::getKnowledgeNum).sum());
        examOutlineService.saveOrUpdate(examOutline);
    }

    public List<ExamKnowledge> matchOutlineByKnowledge(ExamBook examBook, ExamOutline examOutline) {
        List<ExamOutlineDetail> detailList = examOutlineDetailService.lambdaQuery().eq(ExamOutlineDetail::getExamOutlineId, examBook.getExamOutlineId())
                .list();
        // key 本身名称 +父级所有名称
        Map<String, ExamOutlineTag> allTagMap = selectParentList(examBook.getExamOutlineId()).stream()
                .collect(Collectors.toMap(ExamOutlineTag::getId, Function.identity()));

        detailList.forEach(item -> {
            item.setTagAndSelfName(StringUtils.deleteWhitespace(StringUtils.join(allTagMap.get(item.getExamOutlineTagId()).getParentAndSelfNameList(), "") + KnowledgeUnitService.parseCatalogName2KnowledgeFieldName(item.getName())));
        });

        Map<String, ExamOutlineDetail> detailMap = detailList.stream()
                .collect(Collectors.toMap(ExamOutlineDetail::getTagAndSelfName,
                        field -> field, (k1, k2) -> k1, LinkedHashMap::new));

        List<ExamKnowledge> knowledgeList = examKnowledgeService.lambdaQuery().eq(ExamKnowledge::getExamBookId, examBook.getId()).list();

        knowledgeList.forEach(item -> {
            String[] split = item.getBookCatalogPath().split("/");
            for (int i = 0; i < split.length; i++) {
                split[i] = KnowledgeUnitService.parseCatalogName2KnowledgeFieldName(split[i]);
            }
            item.setParentAndSelfName(StringUtils.deleteWhitespace(StringUtils.join(Arrays.asList(split), "")));
        });

        Map<String, ExamKnowledge> knowledgeMap = knowledgeList.stream().collect(Collectors.toMap(ExamKnowledge::getParentAndSelfName, Function.identity()));

        List<ExamKnowledge> examKnowledgeList = new ArrayList<>();
        // 第一种匹配情况,要点+细目+单元 全匹配 图书目录  从最末级匹配
        // 匹配度 100%
        matchByRate(detailMap, knowledgeMap, examKnowledgeList, 1f);
        // 匹配度90%以上
        matchByRate(detailMap, knowledgeMap, examKnowledgeList, 0.9f);
        // 匹配度80%以上
        matchByRate(detailMap, knowledgeMap, examKnowledgeList, 0.8f);
        // 匹配度70%以上
        matchByRate(detailMap, knowledgeMap, examKnowledgeList, 0.7f);
        matchByRate(detailMap, knowledgeMap, examKnowledgeList, 0.6f);
        matchByRate(detailMap, knowledgeMap, examKnowledgeList, 0.5f);
        // 保存知识点
        examKnowledgeService.updateBatchById(examKnowledgeList);
        return examKnowledgeList;
    }

    private void matchByRate(Map<String, ExamOutlineDetail> detailMap, Map<String, ExamKnowledge> knowledgeMap,
                             List<ExamKnowledge> examKnowledgeList, float matchRate) {
        List<String> removeKeyList = Lists.newArrayList();
        for (Map.Entry<String, ExamKnowledge> examKnowledgeEntry : knowledgeMap.entrySet()) {
            for (Map.Entry<String, ExamOutlineDetail> detailEntry : detailMap.entrySet()) {
                float jaccard = StringUtil.jaccard(detailEntry.getKey(), examKnowledgeEntry.getKey());
                if (jaccard >= matchRate) {
                    examKnowledgeEntry.getValue().setOutlineDetailId(detailEntry.getValue().getId());
                    examKnowledgeList.add(examKnowledgeEntry.getValue());
                    removeKeyList.add(examKnowledgeEntry.getKey());
                    break;
                }
            }
        }
        // 已经匹配上的remove掉,防止数据重复
        removeKeyList.forEach(item -> {
            knowledgeMap.remove(item);
        });
    }

    private List<ExamOutlineTag> selectParentList(String examOutlineId) {
        List<ExamOutlineTag> allTagList = examOutlineTagService.lambdaQuery().eq(ExamOutlineTag::getExamOutlineId, examOutlineId).list();
        allTagList.forEach(item -> {
            item.setName(KnowledgeUnitService.parseCatalogName2KnowledgeFieldName(item.getName()));
        });
        Map<String, ExamOutlineTag> id2Item = allTagList.stream().collect(Collectors.toMap(ExamOutlineTag::getId, Function.identity()));
        for (ExamOutlineTag examOutlineTag : allTagList) {
            List<String> parentAndSelfNameList = Lists.newArrayList();
            getAllParentName(examOutlineTag, id2Item, parentAndSelfNameList);
            Collections.reverse(parentAndSelfNameList);
            examOutlineTag.setParentAndSelfNameList(parentAndSelfNameList);
        }
        return allTagList;
    }

    private void getAllParentName(ExamOutlineTag examOutlineTag, Map<String, ExamOutlineTag> id2Item, List<String> parentAndSelfNameList) {
        // 最顶级
        if (StringUtils.isBlank(examOutlineTag.getParentId())) {
            return;
        }
        parentAndSelfNameList.add(examOutlineTag.getName());
        ExamOutlineTag parentTag = id2Item.get(examOutlineTag.getParentId());
        parentAndSelfNameList.add(parentTag.getName());
        getAllParentName(parentTag, id2Item, parentAndSelfNameList);
    }

    private void saveBookCatalog(ExamBook book, ExamBookCatalog bookCatalog) {
        List<ExamBookArticle> articleList = examBookArticleService.lambdaQuery()
                .select(ExamBookArticle::getId, ExamBookArticle::getSituation, ExamBookArticle::getSequenceNumber)
                .eq(ExamBookArticle::getBookId, book.getId()).list().stream().collect(Collectors.toList());
        // 图片url;
        String webPath = "/ymer/action/file/showImage?filename=image/examBook/" + book.getIsbn() + "/pics/";
        // frontCatalogItem;
        List<ExamBookCatalogItem> frontCatalogItemList = bookCatalog.getFrontCatalogItemList();
        // 设置front部分book_article_id;
        if (CollectionUtils.isNotEmpty(frontCatalogItemList)) {
            setArticleField(book, frontCatalogItemList, book.getFrontArticleList(), webPath);
            // 判断是否有article不属于任何目录
            List<String> list = frontCatalogItemList.stream().map(ExamBookCatalogItem::getBookArticleId).collect(Collectors.toList());
            Optional<ExamBookArticle> first = articleList.stream().filter(articleItem -> ExamBookArticle.SITUATION_FRONT.equals(articleItem.getSituation())
                    && !list.contains(articleItem.getId())).findFirst();
            if (first.isPresent()) {
                throw new RuntimeException("sequenceNumber:" + first.get().getSequenceNumber() + "没有对应目录");
            }
            examBookCatalogItemService.saveBatch(frontCatalogItemList);
            // 保存内容;
            List<ExamBookCatalogItemContent> bookCatalogItemContents = frontCatalogItemList.stream().map(ExamBookCatalogItem::getBookCatalogItemContent).collect(Collectors.toList());
            examBookCatalogItemContentService.saveBatch(bookCatalogItemContents);
        }
        // bodyCatalogItem;
        List<ExamBookCatalogItem> bodyCatalogItemList = bookCatalog.getBodyCatalogItemList();
        // 设置body部分book_article_id;
        if (CollectionUtils.isNotEmpty(bodyCatalogItemList)) {
            setArticleField(book, bodyCatalogItemList, book.getBodyArticleList(), webPath);
            // 判断是否有article不属于任何目录
            List<String> list = bodyCatalogItemList.stream().map(ExamBookCatalogItem::getBookArticleId).collect(Collectors.toList());
            Optional<ExamBookArticle> first = articleList.stream().filter(articleItem -> ExamBookArticle.SITUATION_BODY.equals(articleItem.getSituation())
                    && !list.contains(articleItem.getId())).findFirst();
            if (first.isPresent()) {
                throw new RuntimeException("sequenceNumber:" + first.get().getSequenceNumber() + "没有对应目录");
            }
            examBookCatalogItemService.saveBatch(bodyCatalogItemList);
            // 保存内容;
            List<ExamBookCatalogItemContent> bookCatalogItemContents = bodyCatalogItemList.stream().map(ExamBookCatalogItem::getBookCatalogItemContent).collect(Collectors.toList());
            examBookCatalogItemContentService.saveBatch(bookCatalogItemContents);
        }
        // backCatalogItem;
        List<ExamBookCatalogItem> backCatalogItemList = bookCatalog.getBackCatalogItemList();
        // 设置back部分book_article_id;
        if (CollectionUtils.isNotEmpty(backCatalogItemList)) {
            setArticleField(book, backCatalogItemList, book.getBackArticleList(), webPath);
            // 判断是否有article不属于任何目录
            List<String> list = backCatalogItemList.stream().map(ExamBookCatalogItem::getBookArticleId).collect(Collectors.toList());
            Optional<ExamBookArticle> first = articleList.stream().filter(articleItem -> ExamBookArticle.SITUATION_BACK.equals(articleItem.getSituation())
                    && !list.contains(articleItem.getId())).findFirst();
            if (first.isPresent()) {
                throw new RuntimeException("sequenceNumber:" + first.get().getSequenceNumber() + "没有对应目录");
            }
            examBookCatalogItemService.saveBatch(backCatalogItemList);
            // 保存内容;
            List<ExamBookCatalogItemContent> bookCatalogItemContents = backCatalogItemList.stream().map(ExamBookCatalogItem::getBookCatalogItemContent).collect(Collectors.toList());
            examBookCatalogItemContentService.saveBatch(bookCatalogItemContents);
        }
    }

    private void setArticleField(ExamBook book, List<ExamBookCatalogItem> catalogItemList, List<ExamBookArticle> bookArticleList, String webPath) {
        // 准备 sequenceNumber -> entity 映射;
        Map<String, ExamBookCatalogItem> sequenceNumber2EntityMap = catalogItemList.stream().collect(Collectors.toMap(ExamBookCatalogItem::getSequenceNumber, Function.identity()));
        for (ExamBookCatalogItem bookCatalogItem : catalogItemList) {
            // 后续parentId需要使用, 在此赋值id;
            bookCatalogItem.setId(IdWorker.getIdStr());
            // 如果不是一级目录, 则设置父id;
            if (bookCatalogItem.getLayer() != 1) {
                String parentSequenceNumber = bookCatalogItem.getSequenceNumber().substring(0, bookCatalogItem.getSequenceNumber().lastIndexOf("-"));
                bookCatalogItem.setParentId(sequenceNumber2EntityMap.get(parentSequenceNumber).getId());
            }
            bookCatalogItem.setBookId(book.getId());
            // 通过catalogItemSequenceNumber查找articleSequenceNumber;
            Optional<ExamBookArticle> bookArticleOptional = bookArticleList.stream()
                    .filter(article -> Objects.nonNull(article.getUnitContentDom().getElementById("CHP" + bookCatalogItem.getSequenceNumber())))
                    .findFirst();
            if (bookArticleOptional.isPresent()) {
                ExamBookArticle bookArticle = bookArticleOptional.get();
                bookCatalogItem.setBookArticleId(bookArticle.getId());
                bookCatalogItem.setBookArticleSequenceNumber(bookArticle.getSequenceNumber());
                // 处理目录中包含的图片
                bindCataImgSrc(bookCatalogItem, book, webPath);
                // 解析article, 找到当前目录对应的内容;
                bindCatalogItem(bookCatalogItem, bookArticle.getUnitContentDom());
            } else {
                throw new RuntimeException("图书:" + book.getName() + "的目录项:" +
                        bookCatalogItem.getSequenceNumber() + "---" + bookCatalogItem.getLayer() + "无对应内容!");
            }
        }
        // 判断子目录 父节点是否都存在;
        Set<String> bookCatalogItemIdSet = catalogItemList.stream().map(ExamBookCatalogItem::getId).collect(Collectors.toSet());
        Optional<ExamBookCatalogItem> noParentItemOptional = catalogItemList.stream()
                .filter(item -> item.getLayer() > 1)
                .filter(item -> !bookCatalogItemIdSet.contains(item.getParentId()))
                .findAny();
        if (noParentItemOptional.isPresent()) {
            throw new RuntimeException("图书:" + book.getName() + "的目录项: " +
                    noParentItemOptional.get().getSequenceNumber() + "---" +
                    noParentItemOptional.get().getStr() + " 无对应父目录!");
        }
    }

    private void bindCatalogItem(ExamBookCatalogItem bookCatalogItem, Document document) {
        ExamBookCatalogItemContent bookCatalogItemContent = new ExamBookCatalogItemContent();
        bookCatalogItemContent.setBookArticleId(bookCatalogItem.getBookArticleId());
        bookCatalogItemContent.setId(bookCatalogItem.getId());
        bookCatalogItem.setBookCatalogItemContent(bookCatalogItemContent);
        Element cntElement = document.getElementById("CHP" + bookCatalogItem.getSequenceNumber());
        if (Objects.isNull(cntElement)) {
            return;
        }
        bookCatalogItemContent.setTitle(cntElement.outerHtml());
        String articlePart = "";
        while (Objects.nonNull(cntElement.nextElementSibling())) {
            cntElement = cntElement.nextElementSibling();
            if (cntElement.hasAttr("id") && cntElement.attr("id").startsWith("CHP")) {
                break;
            }
            articlePart += System.getProperty("line.separator") + cntElement.outerHtml();
        }
        bookCatalogItemContent.setContent(StringUtils.removeStart(articlePart, System.getProperty("line.separator")));
    }

    private void saveBookPic(ExamBook book) {
        book.getBookPicList().forEach(pic -> pic.setBookId(book.getId()));
        book.getBookTableList().forEach(table -> table.setBookId(book.getId()));
        examBookPicService.saveBatch(book.getBookPicList());
        examBookTableService.saveBatch(book.getBookTableList());
    }

    private void saveBookArticle(ExamBook book) {
        // 图片url;
        String webPath = "/ymer/action/file/showImage?filename=image/examBook/" + book.getIsbn() + "/pics/";
        // 保存前言内容;
        List<ExamBookArticle> frontArticleList = book.getFrontArticleList();
        if (CollectionUtils.isNotEmpty(frontArticleList)) {
            frontArticleList.stream().forEach(bookArticle -> {
                bookArticle.setBookId(book.getId());
                bookArticle.setSituation(ExamBookArticle.SITUATION_FRONT);
                ExamBookUtil.bindImgSrc(book, bookArticle, webPath, ExamBookArticle.SITUATION_FRONT);
            });
            examBookArticleService.saveBatch(frontArticleList);
        }
        // 保存正文内容;
        List<ExamBookArticle> bodyArticleList = book.getBodyArticleList();
        if (CollectionUtils.isNotEmpty(bodyArticleList)) {
            bodyArticleList.stream().forEach(bookArticle -> {
                bookArticle.setBookId(book.getId());
                bookArticle.setSituation(ExamBookArticle.SITUATION_BODY);
                ExamBookUtil.bindImgSrc(book, bookArticle, webPath, ExamBookArticle.SITUATION_BODY);
            });
            examBookArticleService.saveBatch(bodyArticleList);
        }
        // 保存后记内容;
        List<ExamBookArticle> backArticleList = book.getBackArticleList();
        if (CollectionUtils.isNotEmpty(backArticleList)) {
            backArticleList.stream().forEach(bookArticle -> {
                bookArticle.setBookId(book.getId());
                bookArticle.setSituation(ExamBookArticle.SITUATION_BACK);
                ExamBookUtil.bindImgSrc(book, bookArticle, webPath, ExamBookArticle.SITUATION_BACK);
            });
            examBookArticleService.saveBatch(backArticleList);
        }
    }

    private void bindCataImgSrc(ExamBookCatalogItem bookCatalogItem, ExamBook book, String picsTargetDirPath) {
        Document contentDocument = Jsoup.parse(bookCatalogItem.getStr().replaceAll("<Table", "<IMG"));
        Elements imgElements = contentDocument.getElementsByTag("img");
        imgElements.forEach(ele -> {
            // 因图片路径不一致，这里做兼容处理 src="图片名称" src="pics/图片名称"
            // src属性带后缀名， id不带后缀名
            String picName = StringUtils.defaultIfBlank(ele.attr("src"), ele.attr("id"));
            if (StringUtils.isBlank(picName)) {
                throw new RuntimeException("图书: " + book.getName() + ", 内容: " + bookCatalogItem.getStr() + ", 引用生僻字路径为空");
            }
            // 自动添加后缀,默认添加.jpg
            picName = picName.contains(".") ? picName : (picName + ".jpg");
            picName = picName.substring(picName.lastIndexOf("/") + 1);
            if (!book.getPictureSet().contains(picName)) {
                throw new RuntimeException("图书: " + book.getName() + ", 内容: " + bookCatalogItem.getStr() + ", 引用生僻字:" + picName + "不存在");
            }
            ele.attr("src", picsTargetDirPath + picName);
            ele.attr("class", "BZ");
        });
        bookCatalogItem.setStr(contentDocument.body().html());
    }


    public List<CascaderOption> getTree(String outLineId) {
        List<CascaderOption> list = Lists.newArrayList();

        examOutlineTagService.lambdaQuery().eq(ExamOutlineTag::getExamOutlineId, outLineId).list().forEach(item -> {
            list.add(new CascaderOption(item.getId(), item.getParentId(), item.getName()));
        });

        examOutlineDetailService.lambdaQuery().eq(ExamOutlineDetail::getExamOutlineId, outLineId).list().forEach(item -> {
            list.add(new CascaderOption(item.getId(), item.getExamOutlineTagId(), item.getName()));
        });

        Map<String, CascaderOption> id2item = list.stream().collect(Collectors.toMap(CascaderOption::getId, item -> item));
        for (CascaderOption cascaderOption : list) {
            if (StringUtils.isBlank(cascaderOption.getParentId())) {
                continue;
            } else {
                CascaderOption parentNode = id2item.get(cascaderOption.getParentId());
                if (CollectionUtils.isEmpty(parentNode.getChildren())) {
                    parentNode.setChildren(Lists.newArrayList());
                }
                parentNode.getChildren().add(cascaderOption);
            }
        }
        return list.stream().filter(item -> StringUtils.isBlank(item.getParentId())).collect(Collectors.toList());
    }

    public String getContentByCatalogId(ExamBook examBook, ExamBookCatalogItem examBookCatalogItem) {
        // 获取当前对应article;
        ExamBookArticle bookArticle = examBook.getBodyArticleList().stream().filter(item -> item.getId().equals(examBookCatalogItem.getBookArticleId()))
                .findFirst().get();
        Element crtElement = Jsoup.parse(bookArticle.getUnitContent()).getElementById("CHP" + examBookCatalogItem.getSequenceNumber());
        // 内容 不包含第一行(与目录相同)
        String result = "";
        while (true) {
            Element nextSibling = crtElement.nextElementSibling();
            if (Objects.isNull(nextSibling)) {
                // 当前文章遍历结束, 还未取到完整的内容;
                bookArticle = getNextSibling(bookArticle.getBookId(), bookArticle.getId());
                // 下一篇文章不存在, 结束;
                if (Objects.isNull(bookArticle)) {
                    break;
                }
                nextSibling = Jsoup.parse(bookArticle.getUnitContent()).body().children().first();
            }
            if (nextSibling.tagName().toUpperCase().startsWith("H")
                    && !nextSibling.attr("id").startsWith("CHP" + examBookCatalogItem.getSequenceNumber())) {
                break;
            }
            result += "\n" + nextSibling.outerHtml();
            crtElement = nextSibling;
        }
        return result;
    }
    public ExamBookArticle getNextSibling(String bookId, String bookArticleId) {
        LambdaQueryChainWrapper<ExamBookArticle> query = examBookArticleService.lambdaQuery()
                .eq(ExamBookArticle::getBookId, bookId)
                .gt(ExamBookArticle::getId, bookArticleId).orderByAsc(ExamBookArticle::getId);
        return query.list().get(0);
    }

    public ExamKnowledge splitExamKnowledge(ExamKnowledge examKnowledge) {
        LinkedHashMap<String, String> name2ContentMap = Maps.newLinkedHashMap();
        // 获取最大h标签;
        Element body = Jsoup.parse(examKnowledge.getKnowledgeText()).body();
        OptionalInt optionalInt = body.getAllElements().stream()
                .map(ele -> ele.tagName().toLowerCase())
                .filter(tagName -> tagName.startsWith("h"))
                .map(tagName -> tagName.replaceAll("\\D", ""))
                .mapToInt(Integer::parseInt).min();
        if (optionalInt.isPresent()) {
            // 按照最大h标签进行切割;
            String topTagName = "h" + optionalInt.getAsInt();
            String selfContent = "";
            String crtTitle = "";
            for (Element child : body.children()) {
                if (StringUtils.equals(topTagName, child.tagName().toLowerCase())) {
                    crtTitle = KnowledgeUnitService.parseCatalogName2KnowledgeFieldName(child.text());
                    name2ContentMap.put(crtTitle, child.outerHtml());
                } else if (StringUtils.isEmpty(crtTitle)) {
                    selfContent += child.outerHtml();
                    if (StringUtils.isNotEmpty(selfContent)) {
                        name2ContentMap.put(Const.KNOWLEDGE_UNIT_FIELD_SELF, selfContent);
                    }
                } else {
                    name2ContentMap.put(crtTitle, name2ContentMap.get(crtTitle) + child.outerHtml());
                }
            }
        }
        // 没有h标签, 全部内容为无标题字段内容;
        else {
            name2ContentMap.put(Const.KNOWLEDGE_UNIT_FIELD_SELF, examKnowledge.getKnowledgeText());
        }
        // 内容map, 重新计算 h 标签 转为 p class=h
        String bookKnowledgeContent = "";
        for (String key : name2ContentMap.keySet()) {
            String content = name2ContentMap.get(key);
            Element itemBody = Jsoup.parse(content).body();
            // 例如 如果段落内只有 h3, h5, h8, 则数组存储[3,  5, 8], 应该转为 p class = h2, h3, h4
            int[] hArray = itemBody.getAllElements().stream()
                    .map(ele -> ele.tagName().toLowerCase())
                    .filter(tagName -> tagName.startsWith("h"))
                    .map(tagName -> tagName.replaceAll("\\D", ""))
                    .mapToInt(Integer::parseInt).sorted()
                    .distinct().toArray();
            for (int i = 0; i < hArray.length; i++) {
                String tagName = "h" + hArray[i];
                String className = "h" + (i + 1);
                itemBody.getElementsByTag(tagName)
                        .forEach(item -> item.tagName("p").addClass(className));
            }
            bookKnowledgeContent += itemBody.html();
        }
        examKnowledge.setKnowledgeText(bookKnowledgeContent);

        return examKnowledge;
    }

    public List<CascaderOption> selectAllOutlineTree() {
        List<CascaderOption> list = Lists.newArrayList();
        // 大纲
        examOutlineService.lambdaQuery().eq(ExamOutline::getImportStatus, Const.IMPORT_STATUS_SUCCESS).list().forEach(item -> {
            list.add(new CascaderOption(item.getId(), null, item.getName()));
        });
        // 细目
        examOutlineTagService.lambdaQuery().list().forEach(item -> {
            if (StringUtils.isBlank(item.getParentId())) {
                list.add(new CascaderOption(item.getId(), item.getExamOutlineId(), item.getName()));
            } else {
                list.add(new CascaderOption(item.getId(), item.getParentId(), item.getName()));
            }
        });
        // 要点
        examOutlineDetailService.lambdaQuery().list().forEach(item -> {
            list.add(new CascaderOption(item.getId(), item.getExamOutlineTagId(), item.getName()));
        });

        Map<String, CascaderOption> id2item = list.stream().collect(Collectors.toMap(CascaderOption::getId, item -> item));
        for (CascaderOption cascaderOption : list) {
            if (StringUtils.isBlank(cascaderOption.getParentId())) {
                continue;
            } else {
                CascaderOption parentNode = id2item.get(cascaderOption.getParentId());
                if (CollectionUtils.isEmpty(parentNode.getChildren())) {
                    parentNode.setChildren(Lists.newArrayList());
                }
                parentNode.getChildren().add(cascaderOption);
            }
        }
        return list.stream().filter(item -> StringUtils.isBlank(item.getParentId())).collect(Collectors.toList());
    }
}
